package de.koller.worlders.engine.entity.mo.phsics;

import de.koller.oceanFramework.OceanException;
import de.koller.oceanFramework.math.Vec;
import de.koller.worlders.engine.entity.IAABB;
import de.koller.worlders.engine.entity.deco.Deco;
import de.koller.worlders.engine.entity.plattform.Plattform;
import de.koller.worlders.engine.entity.zone.Zone;
import de.koller.worlders.engine.tiles.MOTile;
import de.koller.worlders.engine.tiles.Tile;
import java.util.*;

public class CollisionIterator {

	private final PhysicsMO mo;
	private Set<IAABB> aabbs = new HashSet<>();
	
	public CollisionIterator( PhysicsMO mo ) {
		this.mo = mo;
	}

	public void step( boolean x ) {
		
		updateAABBs();
		mo.getMomov().getContacts().clear();
		mo.getMomov().getContacts().addAll( getAABBsWithCollision( x ) );		
	}
	
	private List<IAABB> getAABBsWithCollision( final boolean x ) {
		List<IAABB> intersections = new ArrayList<>();
		
		for( IAABB a : aabbs )
			if( isAABBIntersection( a, x ) ) {
				if( a instanceof Zone )
					intersections.add( a );
				else if( a instanceof Plattform && !isAABBIntersectionWithoutMove( a ) )
					intersections.add( a );
				else if( a instanceof Tile ) {
					if( ((Tile)a).getTileEntity() instanceof Zone || !isAABBIntersectionWithoutMove( a ) )
						intersections.add( a );
				}
			}
		
		Collections.sort( intersections, new Comparator<IAABB> () {

			@Override
			public int compare( IAABB a, IAABB b ) {
				double wa = CollisionIterator.this.wayTo( a, x );
				double wb = CollisionIterator.this.wayTo( b, x );
				
				if( (x ? CollisionIterator.this.mo.getMomov().getWay().getX() : 
						CollisionIterator.this.mo.getMomov().getWay().getY() ) > 0 ) {
					if( wa > wb )
						return -1;
					else
						return 1;
				} else {
					if( wa > wb )
						return 1;
					else
						return -1;
				}
			}
		
		} );
		
		List<IAABB> result = new ArrayList<>();
		boolean pFound = false;
		double pP = 0;
		for( IAABB a : intersections ) {
			
			IAABB e;
			
			if( a instanceof Tile ) {
				Tile t = (Tile)a;
				e = t.getTileEntity();
			} else {
				e = a;
			}
			
			if( e == null )
				continue;
			if( e instanceof  Zone )
				result.add( e );
			else if( e instanceof Plattform ) {
				if( pFound == false || pP == ( x ? e.getPosition().getX() : e.getPosition().getY() ) ) {
					result.add( e );
					pFound = true;
					pP = x ? e.getPosition().getX() : e.getPosition().getY();
				} else
					break;
			} else 
				throw new OceanException( "unsupported type: " + e );
		}
		
		return result;
	}
	
	private boolean isAABBIntersectionWithoutMove( IAABB a ) {
		return isAABBIntersection( mo, a );
	}
	
	public static boolean isAABBIntersection( IAABB a, IAABB b ) {
		if( a.getPosition().getX() + a.getSize().getX() < b.getPosition().getX() )
			return false;
		if( a.getPosition().getX() > b.getPosition().getX() + b.getSize().getX() )
			return false;
		if( a.getPosition().getY() + a.getSize().getY() < b.getPosition().getY() )
			return false;
		if( a.getPosition().getY() > b.getPosition().getY() + b.getSize().getY() )
			return false;
		
		return true;
	}
	
	private double wayTo( IAABB t, boolean x ) {
		double pt = x ? t.getPosition().getX() : t.getPosition().getY();
				
		if( t instanceof MOTile )
			pt -= x ?   ( ((PhysicsMO)((MOTile)t).getMo()).getMomov().getWay().getX() ) :
						( ((PhysicsMO)((MOTile)t).getMo()).getMomov().getWay().getY() );
		
		return ( x ? this.mo.getPosition().getX() : this.mo.getPosition().getY() ) - pt;
	}
	
	private boolean isAABBIntersection( IAABB t, boolean x ) {
		if( t == null )
			return false;
		
		if( t instanceof MOTile && ((MOTile)t).getMo() == this.mo )
			return false;
		if( mo.getMoTile() != null && t == mo.getMoTile().getTileEntity() )
			return false;
		
		Vec wayTA = mo.getMomov().getWay().newInstance();
		if( t instanceof MOTile )
			wayTA.subtract( ((PhysicsMO)((MOTile)t).getMo()).getMomov().getWay() );
		
		Vec newPosition = mo.getPosition().newInstance().add( wayTA.exclude( x, !x ) );
		
		//@todo check for tunneling!
		if( newPosition.getX() + mo.getSize().getX() < t.getPosition().getX() )
			return false;
		if( newPosition.getX() > t.getPosition().getX() + t.getSize().getX() )
			return false;
		if( newPosition.getY() + mo.getSize().getY() < t.getPosition().getY() )
			return false;
		if( newPosition.getY() > t.getPosition().getY() + t.getSize().getY() )
			return false;
		
		return true;
	}
	
	private void updateAABBs() {
		updateAABBs( aabbs, mo );
	}
	
	public static void updateAABBs( Set<IAABB> set, PhysicsMO mo ) {
		set.clear();
		
		if( mo.getMoTile() == null /*|| mo.getMoTile().getTileEntity() instanceof Zone == false*/ )
			set.addAll( mo.wroot.getEntityContainer().getMoTiles() );
		
		double minx = Math.min( mo.getPosition().getX(), mo.getPosition().getX() + mo.getMomov().getWay().getX() );
		double miny = Math.min( mo.getPosition().getY(), mo.getPosition().getY() + mo.getMomov().getWay().getY() );
		double maxx = Math.max( mo.getPosition().getX() + mo.getSize().getX(), 
								mo.getPosition().getX() + mo.getSize().getX() + mo.getMomov().getWay().getX() );
		double maxy = Math.max( mo.getPosition().getY() + mo.getSize().getY(), 
								mo.getPosition().getY() + mo.getSize().getY() + mo.getMomov().getWay().getY() );
		
		minx = Math.floor( minx ); miny =  Math.floor( miny );
		maxx = Math.floor( maxx + 1 ); maxy =  Math.floor( maxy + 1 );
		
		for( int x = (int)minx; x < (int)maxx; x++ )
			for( int y = (int)miny; y < (int)maxy; y++ ) {
				Tile t = mo.wroot.getTileMap().getTileAt( x+.1f, y+.1f ).getTile();
				if( t != null ) {
					set.add( t );
					
					Deco d = t.getDeco();
					if( d != null && d.getDecoEntity() != null )
						set.add( d.getDecoEntity() );
				}
			}
	}
}
